#ifndef MATHTOOLS_VECTOR3D_H_

#define MATHTOOLS_VECTOR3D_H_

#include <iostream>
#include <math.h>
#include "CPMath.h"

using namespace std;

namespace math_tools
{
	class Vector3D
	{
	public:
		enum TStatus
		{
			INVALID, DEFAULT, UNIT
		};

	private:
		double _x;
		double _y;
		double _z;

		TStatus _Status;

		//
		// Vstup a vystup
		//
		ostream &write(ostream &out) const;
		istream &read(istream &in);

	public:
		//
		// Constructors/Destructors
		//
		Vector3D() :
			_x(0.0), _y(0.0), _z(0.0), _Status(DEFAULT)
		{
		}

		Vector3D(double x, double y, double z) :
			_x(x), _y(y), _z(z), _Status(DEFAULT)
		{
		}

		// Selectors
		double getX() const
		{
			return _x;
		}

		double getY() const
		{
			return _y;
		}

		double getZ() const
		{
			return _z;
		}

		int isUnit() const
		{
			return _Status == UNIT;
		}

		int isDefault() const
		{
			return _Status == DEFAULT;
		}

		int isValid() const
		{
			return _Status != INVALID;
		}

		//
		// Setters
		//
		void setX(double x)
		{
			this->_x = x;
		}

		void setY(double y)
		{
			this->_y = y;
		}

		void setZ(double z)
		{
			this->_z = z;
		}

		//
		// Vector status change
		//
		Vector3D &unit();

		static Vector3D &unit(const Vector3D &v, Vector3D &result)
		{
			result = v;
			return result.unit();
		}

		static Vector3D unit(const Vector3D &v)
		{
			return Vector3D(v).unit();
		}

		Vector3D &Default();

		static Vector3D Default(const Vector3D &v, Vector3D &result)
		{
			result = v;
			return result.Default();
		}

		static Vector3D Default(const Vector3D &v)
		{
			return Vector3D(v).Default();
		}

		//
		// Magnitude.
		//
		double mag() const
		{
			return (isValid() ? (isUnit() ? 1.0 : sqrt(sqr(getX()) + sqr(getY()) + sqr(getZ()))) : 0.0);
		}

		double magSqr() const
		{
			return (isValid() ? (isUnit() ? 1.0 : sqr(getX()) + sqr(getY()) + sqr(getZ())) : 0.0);
		}

		//
		// Dot product.
		//
		double dot(const Vector3D &v) const
		{
			return ((isValid() && v.isValid()) ? (getX() * v.getX() + getY() * v.getY() + getZ() * v.getZ()) : 0.0);
		}

		static double dot(const Vector3D &v1, const Vector3D &v2)
		{
			return v1.dot(v2);
		}

		//
		// Aritmetic operations
		//
		static Vector3D &add(const Vector3D &v1, const Vector3D &v2, Vector3D &result);

		static Vector3D &subtract(const Vector3D &v1, const Vector3D &v2, Vector3D &result);

		static Vector3D &cross(const Vector3D &v1, const Vector3D &v2, Vector3D &result);

		static Vector3D &invert(const Vector3D &v1, Vector3D &result);

		static Vector3D &multiply(const Vector3D &v1, const double &scale, Vector3D &result);

		//
		// Operator overloading
		//
		Vector3D operator-() const
		{
			Vector3D tv;
			return invert(*this, tv);
		}

		Vector3D &operator+=(const Vector3D &v)
		{
			return add(*this, v, *this);
		}

		Vector3D &operator-=(const Vector3D &v)
		{
			return subtract(*this, v, *this);
		}

		Vector3D &operator*=(const Vector3D &v)
		{
			Vector3D tv(*this);
			return cross(tv, v, *this);
		}

		Vector3D &operator*=(const double &scale)
		{
			return multiply(*this, scale, *this);
		}

		Vector3D operator+(const Vector3D &v) const
		{
			Vector3D tv;
			return add(*this, v, tv);
		}

		Vector3D operator-(const Vector3D &v) const
		{
			Vector3D tv;
			return subtract(*this, v, tv);
		}

		Vector3D operator*(const Vector3D &v) const
		{
			Vector3D tv;
			return cross(*this, v, tv);
		}

		Vector3D operator*(const double &scale) const
		{
			Vector3D tv;
			return multiply(*this, scale, tv);
		}

		Vector3D operator*(const float &scale) const
		{
			Vector3D tv;
			return multiply(*this, scale, tv);
		}

		//
		// Streams
		//
		friend ostream &operator<<(ostream &out, const Vector3D &o)
		{
			return o.write(out);
		}

		friend istream &operator>>(istream &in, Vector3D &o)
		{
			return o.read(in);
		}

	};
}

#endif

